﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace ConsoleApplication1
{
    static class DynamicMethodHelper
    {
        private readonly static ConcurrentDictionary<Type, Func<object, NamedArgument[]>> propertyGetterCache = new ConcurrentDictionary<Type, Func<object, NamedArgument[]>>();

        public static NamedArgument[] GetProperties(object instance)
        {
            var func = propertyGetterCache.GetOrAdd(instance.GetType(), key =>
                CreateDelegate(instance));

            return func(instance);
        }

        private static readonly ConstructorInfo NamedArgument = typeof(NamedArgument).GetConstructor(new Type[] { typeof(Type), typeof(string), typeof(object) });
        private static readonly MethodInfo GetTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle");
        private static Func<object, NamedArgument[]> CreateDelegate(object instance)
        {
            DynamicMethod method = new DynamicMethod("EmitPropertyGetter", typeof(NamedArgument[]), new Type[] { typeof(object) }, instance.GetType(), true);
            var il = method.GetILGenerator();
            var type = instance.GetType();
            var properties = type.GetProperties();
          
            var local = il.DeclareLocal(typeof(NamedArgument[]));
            il.EmitInt(properties.Length);
            il.Emit(OpCodes.Newarr, typeof(NamedArgument));
            il.Emit(OpCodes.Stloc_0);

            for (var i = 0; i < properties.Length; i++)
            {
                var property = properties[i];
                il.Emit(OpCodes.Ldloc_0);
                il.EmitInt(i);
                il.Emit(OpCodes.Ldtoken, property.PropertyType);
                il.Emit(OpCodes.Call, GetTypeFromHandle);
                il.Emit(OpCodes.Ldstr, property.Name);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Callvirt, property.GetGetMethod());
                if (property.PropertyType.IsValueType)
                    il.Emit(OpCodes.Box, property.PropertyType);
                il.Emit(OpCodes.Newobj, NamedArgument);
                il.Emit(OpCodes.Stelem, typeof(NamedArgument));
            }

            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);
            return (Func<object, NamedArgument[]>)method.CreateDelegate(typeof(Func<object, NamedArgument[]>));
        }

        internal static void EmitInt(this ILGenerator ilGenerator, int value)
        {
            switch (value)
            {
                case -1:
                    ilGenerator.Emit(OpCodes.Ldc_I4_M1);
                    break;
                case 0:
                    ilGenerator.Emit(OpCodes.Ldc_I4_0);
                    break;
                case 1:
                    ilGenerator.Emit(OpCodes.Ldc_I4_1);
                    break;
                case 2:
                    ilGenerator.Emit(OpCodes.Ldc_I4_2);
                    break;
                case 3:
                    ilGenerator.Emit(OpCodes.Ldc_I4_3);
                    break;
                case 4:
                    ilGenerator.Emit(OpCodes.Ldc_I4_4);
                    break;
                case 5:
                    ilGenerator.Emit(OpCodes.Ldc_I4_5);
                    break;
                case 6:
                    ilGenerator.Emit(OpCodes.Ldc_I4_6);
                    break;
                case 7:
                    ilGenerator.Emit(OpCodes.Ldc_I4_7);
                    break;
                case 8:
                    ilGenerator.Emit(OpCodes.Ldc_I4_8);
                    break;
                default:
                    if (value > -129 && value < 128)
                        ilGenerator.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
                    else
                        ilGenerator.Emit(OpCodes.Ldc_I4, value);
                    break;
            }
        }
    }
}

